

function Level(cave) {
    // create a dungeon
    this.dungeon = new Dungeon(100, 100);
    this.dungeon.generate(cave);
	this.dungeon.generateComplexMap();
    
    // the current collision map for the dungeon
    this.collisionMap = this.dungeon.getCollisionMap();

	
	
	// AStar for the map
	this.astar = new AStar(this.collisionMap);
	this.astar.configuration.diagonal = true;

    // the tiles in the map
    this.tiles = this.dungeon.getFlattenedTiles();
	this.tilesComplex = this.dungeon.getComplexMap();
    
    // basic player object
	this.playerPos = { x: 0, y: 0 };
	
	//Entity
	this.entity = new Array();
    
    this.width = function() { return this.dungeon.size.x * tileSize; };
    this.height = function() { return this.dungeon.size.y * tileSize; };
	
	this.Init = function() {
		var x;
		var y;
		var j = 0;
		
		for (var i = this.dungeon.rooms.length-1; i >= 0; i--) {
		
			x = this.dungeon.rooms[i].pos.x+this.dungeon.rooms[i].size.x/2;
			y = this.dungeon.rooms[i].pos.y+this.dungeon.rooms[i].size.y/2;
		
			// Animals
			this.entity[j] = new Animals();
			this.entity[j].setLife(1);
			
			this.entity[j].pos.x = ((x) * tileSize) + tileSize / 2 - this.entity[j].size.x / 2;
			this.entity[j].pos.y = ((y) * tileSize) + tileSize / 2 - this.entity[j].size.y / 2;
			
			j++;
			
			// Ennemies
			this.entity[j] = new Animals();//new Ennemies();
			this.entity[j].setLife(1);
			//this.entity[j].setValueAttack(0.1);
				
			this.entity[j].pos.x = ((x) * tileSize) + tileSize / 2 - this.entity[j].size.x / 2;
			this.entity[j].pos.y = ((y) * tileSize) + tileSize / 2 - this.entity[j].size.y / 2;	
			
			j++;	

			// EnnemiesAStar
			this.entity[j] = new Animals();//new EnnemiesAStar();
			this.entity[j].setLife(1);
			//this.entity[j].setValueAttack(0.25);
			//this.entity[j].setRadius(5);
				
			this.entity[j].pos.x = ((x) * tileSize) + tileSize / 2 - this.entity[j].size.x / 2;
			this.entity[j].pos.y = ((y) * tileSize) + tileSize / 2 - this.entity[j].size.y / 2;	
			
			j++;		
		}
	}
	
	this.updateEntity = function(elapsed, keysDown) {
		for (var i = this.entity.length-1; i >= 0; i--) {
			this.entity[i].update(elapsed, keysDown);
		}
	}
	
	this.drawEntity = function(context, camera) {
		for (var i = this.entity.length-1; i >= 0; i--) {
			this.entity[i].draw(context, camera);
		}
	}

    this.update = function(elapsed, keysDown) {       
        // compute the player's center
        var cx = Math.floor(player.pos.x / tileSize);
        var cy = Math.floor(player.pos.y / tileSize);
        
        // the return value for the destination. -1 means go up a floor, 1 means go down a floor
        var dest = 0;
        
        // tracks if the player is on stairs this frame
        var onStairs = false;
        
        // grab the new current list of rooms
        var rooms = this.dungeon.roomGrid[cy][cx];
        for (var i = 0; i < rooms.length; i++) {
            var r = rooms[i];
            
            // get the player's center in room coordinates
            var lx = cx - r.pos.x;
            var ly = cy - r.pos.y;
            
            // if we're on the up stairs, return -1 to indicate we want to move up
            if (r.tiles[ly][lx] == Tiles.Stairs_up) {
                onStairs = true;
                
                if (!player.onStairs) {
                    dest = -1;
                    break;
                }
            }
            
            // if we're on the down stairs, return 1 to indicate we want to move down
            if (r.tiles[ly][lx] == Tiles.Stairs_down) {
                onStairs = true;
                
                if (!player.onStairs) {
                    dest = 1;
                    break;
                }
            }
        }
        
        // update the player's "onStairs" property
        player.onStairs = onStairs;
        
        // return our destination
        return dest;
    };

    // x0/y0 == the player
    // x1/y1 == the tile
    this.isTileVisible = function(x0, y0, x1, y1, radius) {
        // los visibility, we want to do a basic line of sight algorithm
            // if one or both points are outside of this map, we discount it from the checks
            if (x0 < 0 || x0 >= this.dungeon.size.x || x1 < 0 || x1 >= this.dungeon.size.x || 
                y0 < 0 || y0 >= this.dungeon.size.y || y1 < 0 || y1 >= this.dungeon.size.y) {
                return true;
            }
			
			if (!isInArea(x0, y0, x1, y1, radius))
				return false;

            // get the deltas and steps for both axis
            var dx = Math.abs(x1 - x0);
            var dy = Math.abs(y1 - y0);
            var sx = x0 < x1 ? 1 : -1;
            var sy = y0 < y1 ? 1 : -1;

            // stores an error factor we use to change the axis coordinates
            var err = dx - dy;

            while (x0 != x1 || y0 != y1)
            {
                // check our collision map to see if this tile blocks visibility
                if (this.collisionMap[y0][x0] == Tiles.Wall)
                    return false;

                // check our error value against our deltas to see if 
                // we need to move to a new point on either axis
                var e2 = 2 * err;
                if (e2 > -dy)
                {
                    err -= dy;
                    x0 += sx;
                }
                if (e2 < dx)
                {
                    err += dx;
                    y0 += sy;
                }
            }

            // if we're here we hit no occluders and therefore can see this tile
            return true;
    };
    
    this.draw = function(context, camera) {
        // compute the player's center in tile space for the tile visibility checks
        var cx = Math.floor((player.pos.x) / tileSize);
        var cy = Math.floor((player.pos.y) / tileSize);

        // calculate the base tile coordinates using the camera
        var baseTileX = Math.floor(camera.x / tileSize) - 1;
        var baseTileY = Math.floor(camera.y / tileSize) - 1;

        // calculating the pixel offset based on the camera
        var pixelOffsetX = ((camera.x % tileSize) + tileSize) % tileSize;
        var pixelOffsetY = ((camera.y % tileSize) + tileSize) % tileSize;

        // calculate the min and max X/Y values
        var pixelMinX = -pixelOffsetX - tileSize;
        var pixelMinY = -pixelOffsetY - tileSize;
        var pixelMaxX = canvas.width + tileSize - pixelOffsetX;
        var pixelMaxY = canvas.height + tileSize - pixelOffsetY;

        // loop over each row, using both tile coordinates and pixel coordinates
        for (var tileY = baseTileY, y = pixelMinY; y < pixelMaxY; tileY++, y += tileSize) {
            // verify this row is actually inside the dungeon
            if (tileY < 0 || tileY >= this.dungeon.size.y) { continue; }

            // loop over each column, using both tile coordinates and pixel coordinates
            for (var tileX = baseTileX, x = pixelMinX; x < pixelMaxX; tileX++, x += tileSize) {
                // verify this column is actually inside the dungeon
                if (tileX < 0 || tileX >= this.dungeon.size.x) { continue; }

                // get the current tile and make sure it's valid
                var tile = this.tilesComplex[tileY][tileX];
                if (tile != null) {
                    // test if the tile is visible
                    var canBeSeen = this.isTileVisible(cx, cy, tileX, tileY, player.getRadius());

                    // make sure the tile stores a record if it's ever been seen
                    if (canBeSeen)
                        tile.HasBeenSeen = true;

                    // if we have ever seen this tile, we need to draw it
                    if (tile.HasBeenSeen) {
                        // choose the color by the type and whether the tile is currently visible
						context.font = "16px Arial";
						switch (tile.type) {
							case ComplexTiles.Concave_NW:
							case ComplexTiles.Concave_NE: 
							case ComplexTiles.Concave_SW: 
							case ComplexTiles.Concave_SE:
							
							case ComplexTiles.Convex_SW: 	
							case ComplexTiles.Convex_NW:								
							case ComplexTiles.Convex_NE:
							case ComplexTiles.Convex_SE: 
							
							case ComplexTiles.Side_S:  
							case ComplexTiles.Side_E:  
							case ComplexTiles.Side_W:
							case ComplexTiles.Side_N: 
							
								context.fillStyle = canBeSeen ? '#8B4513' : '#61300D';
								context.fillText("\u2593", x-2, y);
								context.fillText("\u2593", x+2, y);break;
							case ComplexTiles.Stairs_up:
								context.fillStyle = '#F2CD27';
								context.fillText("U", x, y);break;
							case ComplexTiles.Stairs_down:
								context.fillStyle = '#7A5A0D';
								context.fillText("D", x, y);break;
							case ComplexTiles.Inner:
								context.fillStyle = canBeSeen ? '#B8860B' : '#705104';
								context.fillText(".", x, y);break;
						}
                    }
                }
            }
        }	
    };
	
	this.drawMiniMap = function(context, camera, offsetX, offsetY, sizeOfTile) {	
		camera.x = 0;
		camera.y = 0;
		
		//var offsetX = canvas.width-100;
		//var offsetY = 0;

        // calculate the base tile coordinates using the camera
        var baseTileX = Math.floor(camera.x / 1) - 1;
        var baseTileY = Math.floor(camera.y / 1) - 1;

        // calculating the pixel offset based on the camera
        // following http://javascript.about.com/od/problemsolving/a/modulobug.htm to fix negative camera values
        var pixelOffsetX = ((camera.x % 1) + 1) % 1;
        var pixelOffsetY = ((camera.y % 1) + 1) % 1;

        // calculate the min and max X/Y values
        var pixelMinX = -pixelOffsetX - 1;
        var pixelMinY = -pixelOffsetY - 1;
        var pixelMaxX = canvas.width + 1 - pixelOffsetX;
        var pixelMaxY = canvas.height + 1 - pixelOffsetY;

        // loop over each row, using both tile coordinates and pixel coordinates
        for (var tileY = baseTileY, y = pixelMinY; y < pixelMaxY; tileY++, y += sizeOfTile) {
            // verify this row is actually inside the dungeon
            if (tileY < 0 || tileY >= this.dungeon.size.y) { continue; }

            // loop over each column, using both tile coordinates and pixel coordinates
            for (var tileX = baseTileX, x = pixelMinX; x < pixelMaxX; tileX++, x += sizeOfTile) {
                // verify this column is actually inside the dungeon
                if (tileX < 0 || tileX >= this.dungeon.size.x) { continue; }

                // get the current tile and make sure it's valid
                var tile = this.tiles[tileY][tileX];
                if (tile != null) {
					// if we have ever seen this tile, we need to draw it
                    //if (tile.HasBeenSeen) 
					{
                        // choose the color by the type and whether the tile is currently visible
                        switch (tile.type) {
                            case Tiles.Wall:
                                context.fillStyle = '#61300D';
								context.fillRect(x+offsetX, y+offsetY, sizeOfTile, sizeOfTile);
                                break;
                            case Tiles.Stairs_down:
                                context.fillStyle = '#7A5A0D';
								context.fillRect(x+offsetX, y+offsetY, sizeOfTile, sizeOfTile);
                                break;
                            case Tiles.Stairs_up:
                                context.fillStyle = '#F2CD27';
								context.fillRect(x+offsetX, y+offsetY, sizeOfTile, sizeOfTile);
                                break;
                        }
                    }
                }
            }
        }
    };
    
    this.moveEntity = function(pos, size, realSize, move) {
        // start with the end goal position
        var endPos = {
            x: pos.x + move.x,
            y: pos.y + move.y
        };
        
        // check X axis motion for collisions
        if (move.x) {
            // calculate the X tile coordinate where we'd like to be
            var offset = (move.x > 0 ? size.x : 0);
            var x = Math.floor((pos.x + move.x + offset) / tileSize);
            
            // figure out the range of Y tile coordinates that we can collide with
            var start = Math.floor(pos.y / tileSize);
            var end = Math.ceil((pos.y + size.y) / tileSize);
            
            // determine whether these tiles are all inside the map
            if (end >= 0 && start < this.dungeon.size.y && x >= 0 && x < this.dungeon.size.x) {
                // go down each of the tiles along the Y axis
                for (var y = start; y < end; y++) {
                    // if there is a wall in the tile
                    if (this.collisionMap[y][x] == Tiles.Wall) {
                        // we adjust our end position accordingly
                        endPos.x = x * tileSize - offset + (move.x < 0 ? tileSize : 0);
                        break;
                    }
                }   
            }
        }

        // then check Y axis motion for collisions
        if (move.y) {      
            // calculate the X tile coordinate where we'd like to be
            var offset = (move.y > 0 ? realSize.y : 0);
            var y = Math.floor((pos.y + move.y + offset) / tileSize);
            
            // figure out the range of X tile coordinates that we can collide with
            var start = Math.floor(endPos.x / tileSize);
            var end = Math.ceil((endPos.x + realSize.x) / tileSize);
            
            // determine whether these tiles are all inside the map
            if (end >= 0 && start < this.dungeon.size.x && y >= 0 && y < this.dungeon.size.y) {
                // go across each of the tiles along the X axis
                for (var x = start; x < end; x++) {
                    // if there is a wall in the tile
                    if (this.collisionMap[y][x] == Tiles.Wall) {
                        // we adjust our end position accordingly
                        endPos.y = y * tileSize - offset + (move.y < 0 ? tileSize : 0);
                        break;
                    }
                }
            }
        }     
        
        // give back the new position for the object
        return endPos;
    };
	
	this.moveEntityWall = function(pos, size, realSize, move) {
        // start with the end goal position
        var endPos = {
            x: pos.x + move.x,
            y: pos.y + move.y
        };
        var collision = false;
        
        // check X axis motion for collisions
        if (move.x) {
            // calculate the X tile coordinate where we'd like to be
            var offset = (move.x > 0 ? size.x : 0);
            var x = Math.floor((pos.x + move.x + offset) / tileSize);
            
            // figure out the range of Y tile coordinates that we can collide with
            var start = Math.floor(pos.y / tileSize);
            var end = Math.ceil((pos.y + size.y) / tileSize);
            
            // determine whether these tiles are all inside the map
            if (end >= 0 && start < this.dungeon.size.y && x >= 0 && x < this.dungeon.size.x) {
                // go down each of the tiles along the Y axis
                for (var y = start; y < end; y++) {
                    // if there is a wall in the tile
                    if (this.collisionMap[y][x] == Tiles.Wall) {
                        // we adjust our end position accordingly
                        collision = true;
                        break;
                    }
                }   
            }
        }

        // then check Y axis motion for collisions
        if (move.y) {      
            // calculate the X tile coordinate where we'd like to be
            var offset = (move.y > 0 ? realSize.y : 0);
            var y = Math.floor((pos.y + move.y + offset) / tileSize);
            
            // figure out the range of X tile coordinates that we can collide with
            var start = Math.floor(endPos.x / tileSize);
            var end = Math.ceil((endPos.x + realSize.x) / tileSize);
            
            // determine whether these tiles are all inside the map
            if (end >= 0 && start < this.dungeon.size.x && y >= 0 && y < this.dungeon.size.y) {
                // go across each of the tiles along the X axis
                for (var x = start; x < end; x++) {
                    // if there is a wall in the tile
                    if (this.collisionMap[y][x] == Tiles.Wall) {
                        // we adjust our end position accordingly
                        collision = true;
                        break;
                    }
                }
            }
        }     
        
        // give back the new position for the object
        return collision;
    };
}